package com.bclz.laziness
import Stream._
sealed trait Stream[+A] {

  def toList: List[A] = this match {
    case Empty => Nil
    case Cons(hf, tf) => hf() :: tf().toList
  }

  def take(n: Int): Stream[A] = this match {

    case Cons(hf, tf) => {
      if (n > 1) cons(hf(), tf().take(n - 1))
      else Cons(hf, () => empty[A])
    }
    case Empty => empty[A]

  }

  def headOption:Option[A]=this match {
    case Empty => None
    case Cons(hf,_)=>Some(hf())
  }

  def exists(p:A=>Boolean):Boolean= this match {
    case Cons(hf,tf)=>p(hf()) || tf().exists(p)
    case _=>false
  }

  def foldRight[B](b: =>B)(f:(A, =>B)=>B):B=this match {
    case Empty => b
    case Cons(hf,tf)=>f(hf(),tf().foldRight(b)(f))
  }

  def existsViaFoldRight(p:A=>Boolean):Boolean=
     foldRight(false)((a,b)=>p(a)||b)

  def forAllViaFoldRight(f:A=>Boolean):Boolean=
    foldRight(false)((a,b)=> !f(a)||b)

  def takeWhileViaFoldRight(f:A=>Boolean):Stream[A]=
    foldRight[Stream[A]](Empty)((a,b) => if(!f(a)) cons(a,b) else Empty)

  def headOptionViaFoldRight:Option[A]=
    foldRight[Option[A]](None)((a,_)=>Some(a))


  def map[B](f:A=>B):Stream[B]=
    foldRight[Stream[B]](Empty)((a,b) => cons(f(a),b))
  def filter(f:A=>Boolean):Stream[A] =
    foldRight[Stream[A]](Empty)((a,b)=>if(f(a)) cons(a,b) else b )

  def append[B >:A](ba: =>Stream[B]) :Stream[B]=
    foldRight[Stream[B]](ba)((a,b)=>cons(a,b))

  def flatMap[B](f:A=>Stream[B]):Stream[B] =
    foldRight[Stream[B]](empty[B])((h,t)=>f(h) append t)
}

case object Empty extends Stream[Nothing]

case class Cons[+A](head: () => A, tail: () => Stream[A]) extends Stream[A]


object Stream {

  def cons[A](head: => A, tail: => Stream[A]): Stream[A] = {

    lazy val aLazy = head
    lazy val sLazy = tail
    Cons(() => aLazy, () => sLazy)

  }

  def empty[A]: Stream[A] = Empty

  def apply[A](al: A*): Stream[A] = {

    if (al.isEmpty) empty
    else Cons(()=>al.head, ()=>apply(al.tail: _*))

  }

  def constant[A](a:A):Stream[A]=
    cons(a,constant(a))

  def from(n:Int):Stream[Int]=
    cons(n,from(n+1))

  def fibs():Stream[Int]={
    def fib(lastNum:Int,currentNum:Int):Stream[Int]={
      cons(lastNum,fib(currentNum,lastNum+currentNum))
    }
    fib(0,1)
  }

  /**
    * 属于共递归函数，生产数据能力由f函数的结束而结束
    */
  def unfold[A, S](z: S)(f: S => Option[(A, S)]): Stream[A] =
    f(z) match {
      case Some((h,s)) => cons(h, unfold(s)(f))
      case None => empty
    }

  def constantViaUnfold[A](a:A):Stream[A]=
    unfold(a)(as=>Some((as,as)))

  def fromViaUnfold(n:Int):Stream[Int]=
    unfold(n)(as=>Some((as,as+1)))

}
